﻿#define _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>
/***********操作符详解**********
* 
• 算术操作符： + 、 - 、 * 、 / 、 %
• 移位操作符 : << >>
• 位操作符 : &| ^
• 赋值操作符 : = 、 += 、 -= 、 *= 、 /= 、 %= 、 <<= 、 >>= 、 &= 、 |= 、 ^=
• 单⽬操作符： ！、++、--、 & 、 * 、 + 、 - 、~、sizeof、(类型)
• 关系操作符 : > 、 >= 、 < 、 <= 、 == 、 !=
• 逻辑操作符： && 、 ||
• 条件操作符： ? :
• 逗号表达式：,
• 下标引⽤：[]
• 函数调⽤：()
• 结构成员访问：.、->

整数的2进制表⽰⽅法有三种，即原码、反码和补码
有符号整数的三种表⽰⽅法均有符号位和数值位两部分，2进制序列中，最⾼位的1位是被当做符号位，剩余的都是数值位。
符号位都是⽤0表⽰“正”，⽤1表⽰“负”。
正整数的原、反、补码都相同。
负整数的三种表⽰⽅法各不相同。
原码：直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。
反码：将原码的符号位不变，其他位依次按位取反就可以得到反码。
补码：反码 + 1就得到补码。
补码得到原码也是可以使⽤：取反， + 1的操作。
对于整形来说：数据存放内存中其实存放的是补码

*/

/*
移位操作符
<< 左移操作符
>> 右移操作符
注：移位操作符的操作数只能是整数。
对于移位运算符，不要移动负数位，这个是标准未定义的。
*/
//左移操作符
//移位规则：左边抛弃、右边补0
//右移操作符
//移位规则：⾸先右移运算分两种：
//1. 逻辑右移：左边⽤0填充，右边丢弃
//2. 算术右移：左边⽤原该值的符号位填充，右边丢弃
//通常用的算术右移
int main1()
{
	int a = 10;
	//0000 1010 -- 原码（整数原 反 补码相同）
	//左边抛弃、右边补0
	//0001 0100 -- 原码
	int b = a<<1;//左移得值20
	//0000 1010 -- 原码
	//算术右移：左边⽤原该值的符号位填充，右边丢弃 正数符号位是0 填充就是0
	//0000 0101
	int c = a >> 1;//右移得值5
	printf("%d %d\n", b,c);
	return 0;
}

//储存与操作时都是以补码形式 显示是以原码的形式
int main2()
{
	int a = -2;
    //1000 0010 负数第一位符号位为1表示负数
	//1111 1101 取反得反码 取反符号位还是1   取反符号位不变
	//1111 1110 补码(反码+1)
	
	// 补码赋值给b
	//左移：左边抛弃、右边补0
	//1111 1100 补码左移结果
	
	//1000 0011 取反
	//1000 0100 +1 得原码
	int b = a << 1;//左移得值-4

	//1111 1110 -2的补码
	//补码赋值给c
	//算术右移：左边⽤原该值的符号位填充，右边丢弃   负数数符号位是1 填充就是1
	//1111 1111 右移补码结果
	
	//1000 0000 取反
	//1000 0001 +1 得原码 显示原码
	int c = a >> 1;//得-1
	printf("%d %d\n", b, c);
	return 0;
}




//位操作符：& 、 | 、 ^、~
//位操作符有：
//& //按位与
//| //按位或
//^ //按位异或
//~ //按位取反
int main3()
{
	int a = 10;
	//0000 1010 10的原码

	int b = -7;
	//1000 0111 -7的原码
	//1111 1000 -7的反码
	//1111 1001 -7的补码

	int c = a&b;

	//两个补码之间按位与 相同出1 不同出0 和乘法差不多 符号位也要相 与
	//0000 1010 10的补码   
	//1111 1001 -7的补码
	//0000 1000 相与后的补码

	//1111 0111
	//1111 1000
	//得值 8
	printf("%d\n",c);
	return 0;
}

int main4()
{
	int a = 10;
	//0000 1010 10的原码

	int b = -7;
	//1000 0111 -7的原码
	//1111 1000 -7的反码
	//1111 1001 -7的补码

	int c = a | b;

	//两个补码之间按位或 有1出1 全0出0 和加法差不多 符号位也要相 或
	//0000 1010 10的补码   
	//1111 1001 -7的补码
	//1111 1011 相或后的补码

	//1000 0100 取反
	//1000 0101 +1 得原码 显示是原码形式 储存与运算是补码
	//得值 -5
	printf("%d\n", c);
	return 0;
}


int main5()
{
	int a = 10;
	//0000 1010 10的原码

	int b = -7;
	//1000 0111 -7的原码
	//1111 1000 -7的反码
	//1111 1001 -7的补码

	int c = a ^ b;

	//两个补码之间按位 异或 相同为0 相异为1  符号位也要相 异或
	//0000 1010 10的补码   
	//1111 1001 -7的补码
	//1111 0011  相异或后的补码

	//1000 1100 取反
	//1000 1101 +1 得原码 显示是原码形式 储存与运算是补码
	//得值 -13
	printf("%d\n", c);
	return 0;
}

int main6()
{
	int a = 0;
	//0000 0000 0的原码 0是整数
	//1111 1111 ~按位取反后 符号位也要取反 此时符号位变成了负数
	//1000 0000 取反得反码
	//1000 0001 +1得补码
	printf("%d\n", ~a);//得值-1
	return 0;
}

//不增加第三个变量也可以交换参数  但是异或有弊端 不易于理解
//异或是符合交换律的
int main7()
{
	int a = 3;
	int b = 5;
	printf("转换前 a=%d b=%d\n",a,b);

	a = a ^ b;
	//011 3
	//101 5 3和5的八进制相 异或 相同为0 不同为1
	//110 6 异或后得6 
    //a = 6 将值赋予a = 6 
	b = a ^ b;
	//110 6   a = 6
	//101 5   b = 5  
	//011 3   异或得3 
	//b = 3   将值赋予b = 3 
	a = a ^ b;
	//110 6   a = 6
	//011 3   b = 3 
	//101 5   异或得5 
	//a = 5   将值赋予a = 5

	//程序结束后 a = 5，b = 3 就实现了互换
	printf("转换后 a=%d b=%d\n", a, b);
	return 0;
}


//练习1：编写代码实现：求⼀个整数存储在内存中的⼆进制中1的个数。
//int count_bit_one(unsigned int num)
//{
//	int count = 0;
//	while (num)
//	{
//		if (num % 2 == 1)
//			count++;
//		num = num / 2;
//	}
//	return count;
//}

//int count_bit_one(unsigned int num)
//{
//	int count = 0;
//	for (int i = 0; i < 32; i++)
//	{
//		if ((( num >> i) & 1) == 1)//每次右移 i 的位置 对1进行与运算 得1的话就count+1
//			count++;
//	}
//	return count;
//}

int count_bit_one(unsigned int num)
{
	int count = 0;
	while (num)
	{
		num = num & (num - 1);//把n的二进制序列中最右边的1去掉 这样就是有几个1就算几次
			count++;
	}
	return count;
}


int main8()
{
	int num = 0;
	scanf("%d", &num);
	int ret = count_bit_one(num);
	printf("%d\n", ret);
	return 0;
}


//练习2：⼆进制位置0或者置1
//编写代码将13⼆进制序列的第5位修改为1，然后再改回0
int main10()
{
	int a = 13;
	int n = 5;
    a = a | (1 << (n - 1));//29
	printf("%d\n", a);
	a = a & ~(1 << (n - 1));//13
	printf("%d\n", a);
}

/*
逗号表达式
1 exp1, exp2, exp3, …expN
逗号表达式，就是⽤逗号隔开的多个表达式。
逗号表达式，从左向右依次执⾏。整个表达式的结果是最后⼀个表达式的结果。
*/

/*
[ ] 下标引⽤操作符
操作数：⼀个数组名 + ⼀个索引值(下标)
[] 的两个操作数是arr和9。
*/
int main()
{
	int arr[10] = { 1,2,3,4,5 };

	int m = arr[4];//数组中下标是4的元素
	//[] 下标引用操作符 - 操作数是：arr,4
	//3+5, + 是操作符，3和5是操作数
	//
	printf("%d\n", m);

	return 0;
}


//函数调⽤操作符
//接受⼀个或者多个操作数：第⼀个操作数是函数名，剩余的操作数就是传递给函数的参数。
//int Add(int x, int y)
//{
//	return x + y;
//}
//
//int main()
//{
//	printf("hehe\n");//() 就是函数调用操作符,操作数是：
//	printf("%d\n", 100);
//	int ret = Add(3, 5);//Add 3 5 
//	//函数调用操作符最少有几个操作数？
//至少一个操作数 就是函数名
//	return 0;
//}
//



//struct 结构体关键字
//可以创建一个自定义类型
// 
//学生类型 给这个类型起个名
//struct Student
//{
//	//成员变量
//	char name[20]; //字符串类型数组 名字
//	int age;    //年纪
//	float score; //成绩
//}s4, s5, s6;//也可以在 分号前定义全局变量
//
//struct Student s3 = {"小何", 25, 88.8};//全局变量
//
//struct Point
//{
//	int x;
//	int y;
//};//分号不能丢
//
//struct S
//{
//	char ch;
//	struct Point p;
//	int arr[10];
//	double d;
//};//分号不能丢
//
//int main()
//{
//	int a;
// 
// struct Student s1 
// struct Student 这个是自定义结构体类型 相当于int char 的类型 那么用
// s1 是变量 跟 int n的n那么用

//	struct Student s1 = {"小张", 20, 98.0};//局部变量
//	struct Student s2 = {"小李", 18, 69.8};
//	struct Point p = { 10, 20 };
//	struct S s = { 'a',{4,5}, {1,2,3,4,5,6,7},3.14 };
// 
//  要根据类型来给他们赋值‘a’是因为ch是char字符型数据
// {4，5} struct Point 里有两个整型 括起来要和旁边隔离开
// {1,2,3,4,5,6,7} 是数组 数组初始化需要花括号
// 3.14  double d 是浮点数
// 
//	printf("%c\n", s.ch);
//	printf("坐标是:%d %d\n", s.p.x, s.p.y);
//	printf("%d\n", s.arr[0]);
//	printf("%lf\n", s.d);
// 
//结构体成员的直接访问是通过点操作符（.）访问的。点操作符接受两个操作数。 变量s s.ch  变量加 . 加上需要访问的目标 就可以访问
//	//结构体变量.结构体成员名
//
//	return 0;
//}


/*
操作符的属性：优先级、结合性
C语⾔的操作符有2个重要的属性：优先级、结合性，这两个属性决定了表达式求值的计算顺序。
优先级
优先级指的是，如果⼀个表达式包含多个运算符，哪个运算符应该优先执⾏。各种运算符的优先级是
不⼀样的
*/
//相邻的操作符，优先级高的先执行
//相邻的操作符的优先级相同的情况下，结合性说了算
//
int main()
{
	//int r = 3 + 4 * 5; 先会算*法，因为*优先级高于加减
	//int r = (3 + 4) * 5; 想要先加减就括起来
	int r = 3 + 4 + 5;

	return 0;
}
//基本优先级
//• 圆括号（() ）
//• ⾃增运算符（ ++ ），⾃减运算符（ -- ）
//• 单⽬运算符（ + 和 - ）
//• 乘法（ * ），除法（ / ）
//• 加法（ + ），减法（ - ）
//• 关系运算符（ < 、 > 等）
//• 赋值运算符（ = ）